 /*******************************************************************************
  * Copyright (c) 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  ******************************************************************************/

 package org.eclipse.ui.internal.forms;

 import java.io.PrintWriter ;
 import java.io.StringWriter ;
 import java.util.ArrayList ;
 import java.util.Enumeration ;
 import java.util.Hashtable ;
 import java.util.Iterator ;

 import org.eclipse.jface.dialogs.IMessageProvider;
 import org.eclipse.jface.fieldassist.ControlDecoration;
 import org.eclipse.jface.fieldassist.FieldDecoration;
 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CLabel;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.ui.forms.IMessage;
 import org.eclipse.ui.forms.IMessageManager;
 import org.eclipse.ui.forms.IMessagePrefixProvider;
 import org.eclipse.ui.forms.widgets.Hyperlink;
 import org.eclipse.ui.forms.widgets.ScrolledForm;

 /**
  * @see IMessageManager
  */

 public class MessageManager implements IMessageManager {
     private static final DefaultPrefixProvider DEFAULT_PREFIX_PROVIDER = new DefaultPrefixProvider();
     private ArrayList messages = new ArrayList ();
     private Hashtable decorators = new Hashtable ();
     private boolean autoUpdate = true;
     private ScrolledForm scrolledForm;
     private IMessagePrefixProvider prefixProvider = DEFAULT_PREFIX_PROVIDER;
     private int decorationPosition = SWT.LEFT | SWT.BOTTOM;
     private static FieldDecoration standardError = FieldDecorationRegistry
             .getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR);
     private static FieldDecoration standardWarning = FieldDecorationRegistry
             .getDefault().getFieldDecoration(
                     FieldDecorationRegistry.DEC_WARNING);

     private static final String [] SINGLE_MESSAGE_SUMMARY_KEYS = {
             Messages.MessageManager_sMessageSummary,
             Messages.MessageManager_sMessageSummary,
             Messages.MessageManager_sWarningSummary,
             Messages.MessageManager_sErrorSummary };

     private static final String [] MULTIPLE_MESSAGE_SUMMARY_KEYS = {
             Messages.MessageManager_pMessageSummary,
             Messages.MessageManager_pMessageSummary,
             Messages.MessageManager_pWarningSummary,
             Messages.MessageManager_pErrorSummary };

     static class Message implements IMessage {
         Control control;
         Object data;
         Object key;
         String message;
         int type;
         String prefix;

         Message(Object key, String message, int type, Object data) {
             this.key = key;
             this.message = message;
             this.type = type;
             this.data = data;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.dialogs.IMessage#getKey()
          */
         public Object getKey() {
             return key;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.dialogs.IMessageProvider#getMessage()
          */
         public String getMessage() {
             return message;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.dialogs.IMessageProvider#getMessageType()
          */
         public int getMessageType() {
             return type;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.ui.forms.messages.IMessage#getControl()
          */
         public Control getControl() {
             return control;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.ui.forms.messages.IMessage#getData()
          */
         public Object getData() {
             return data;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.ui.forms.messages.IMessage#getPrefix()
          */
         public String getPrefix() {
             return prefix;
         }
     }

     static class DefaultPrefixProvider implements IMessagePrefixProvider {

         public String getPrefix(Control c) {
             Composite parent = c.getParent();
             Control[] siblings = parent.getChildren();
             for (int i = 0; i < siblings.length; i++) {
                 if (siblings[i] == c) {
                     // this is us - go backward until you hit
 // a label-like widget
 for (int j = i - 1; j >= 0; j--) {
                         Control label = siblings[j];
                         String ltext = null;
                         if (label instanceof Label) {
                             ltext = ((Label) label).getText();
                         } else if (label instanceof Hyperlink) {
                             ltext = ((Hyperlink) label).getText();
                         } else if (label instanceof CLabel) {
                             ltext = ((CLabel) label).getText();
                         }
                         if (ltext != null) {
                             if (!ltext.endsWith(":")) //$NON-NLS-1$
 return ltext + ": "; //$NON-NLS-1$
 return ltext + " "; //$NON-NLS-1$
 }
                     }
                     break;
                 }
             }
             return null;
         }
     }

     class ControlDecorator {
         private ControlDecoration decoration;
         private ArrayList controlMessages = new ArrayList ();
         private String prefix;

         ControlDecorator(Control control) {
             this.decoration = new ControlDecoration(control, decorationPosition, scrolledForm.getBody());
         }

         public boolean isDisposed() {
             return decoration.getControl() == null;
         }

         void updatePrefix() {
             prefix = null;
         }

         void updatePosition() {
             Control control = decoration.getControl();
             decoration.dispose();
             this.decoration = new ControlDecoration(control, decorationPosition, scrolledForm.getBody());
             update();
         }

         String getPrefix() {
             if (prefix == null)
                 createPrefix();
             return prefix;
         }

         private void createPrefix() {
             if (prefixProvider == null) {
                 prefix = ""; //$NON-NLS-1$
 return;
             }
             prefix = prefixProvider.getPrefix(decoration.getControl());
             if (prefix == null)
                 // make a prefix anyway
 prefix = ""; //$NON-NLS-1$
 }

         void addAll(ArrayList target) {
             target.addAll(controlMessages);
         }

         void addMessage(Object key, String text, Object data, int type) {
             Message message = MessageManager.this.addMessage(getPrefix(), key,
                     text, data, type, controlMessages);
             message.control = decoration.getControl();
             if (isAutoUpdate())
                 update();
         }

         boolean removeMessage(Object key) {
             Message message = findMessage(key, controlMessages);
             if (message != null) {
                 controlMessages.remove(message);
                 if (isAutoUpdate())
                     update();
             }
             return message != null;
         }

         boolean removeMessages() {
             if (controlMessages.isEmpty())
                 return false;
             controlMessages.clear();
             if (isAutoUpdate())
                 update();
             return true;
         }

         public void update() {
             if (controlMessages.isEmpty()) {
                 decoration.setDescriptionText(null);
                 decoration.hide();
             } else {
                 ArrayList peers = createPeers(controlMessages);
                 int type = ((IMessage) peers.get(0)).getMessageType();
                 String description = createDetails(createPeers(peers), true);
                 if (type == IMessageProvider.ERROR)
                     decoration.setImage(standardError.getImage());
                 else if (type == IMessageProvider.WARNING)
                     decoration.setImage(standardWarning.getImage());
                 decoration.setDescriptionText(description);
                 decoration.show();
             }
         }
     }

     /**
      * Creates a new instance of the message manager that will work with the
      * provided form.
      *
      * @param scrolledForm
      * the form to control
      */
     public MessageManager(ScrolledForm scrolledForm) {
         this.scrolledForm = scrolledForm;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#addMessage(java.lang.Object,
      * java.lang.String, int)
      */
     public void addMessage(Object key, String messageText, Object data, int type) {
         addMessage(null, key, messageText, data, type, messages);
         if (isAutoUpdate())
             updateForm();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#addMessage(java.lang.Object,
      * java.lang.String, int, org.eclipse.swt.widgets.Control)
      */
     public void addMessage(Object key, String messageText, Object data,
             int type, Control control) {
         ControlDecorator dec = (ControlDecorator) decorators.get(control);

         if (dec == null) {
             dec = new ControlDecorator(control);
             decorators.put(control, dec);
         }
         dec.addMessage(key, messageText, data, type);
         if (isAutoUpdate())
             updateForm();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#removeMessage(java.lang.Object)
      */
     public void removeMessage(Object key) {
         Message message = findMessage(key, messages);
         if (message != null) {
             messages.remove(message);
             if (isAutoUpdate())
                 updateForm();
         }
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#removeMessages()
      */
     public void removeMessages() {
         if (!messages.isEmpty()) {
             messages.clear();
             if (isAutoUpdate())
                 updateForm();
         }
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#removeMessage(java.lang.Object,
      * org.eclipse.swt.widgets.Control)
      */
     public void removeMessage(Object key, Control control) {
         ControlDecorator dec = (ControlDecorator) decorators.get(control);
         if (dec == null)
             return;
         if (dec.removeMessage(key))
             if (isAutoUpdate())
                 updateForm();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#removeMessages(org.eclipse.swt.widgets.Control)
      */
     public void removeMessages(Control control) {
         ControlDecorator dec = (ControlDecorator) decorators.get(control);
         if (dec != null) {
             if (dec.removeMessages()) {
                 if (isAutoUpdate())
                     updateForm();
             }
         }
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#removeAllMessages()
      */
     public void removeAllMessages() {
         boolean needsUpdate = false;
         for (Enumeration enm = decorators.elements(); enm.hasMoreElements();) {
             ControlDecorator control = (ControlDecorator) enm.nextElement();
             if (control.removeMessages())
                 needsUpdate = true;
         }
         if (!messages.isEmpty()) {
             messages.clear();
             needsUpdate = true;
         }
         if (needsUpdate && isAutoUpdate())
             updateForm();
     }

     /*
      * Adds the message if it does not already exist in the provided list.
      */

     private Message addMessage(String prefix, Object key, String messageText,
             Object data, int type, ArrayList list) {
         Message message = findMessage(key, list);
         if (message == null) {
             message = new Message(key, messageText, type, data);
             message.prefix = prefix;
             list.add(message);
         } else {
             message.message = messageText;
             message.type = type;
             message.data = data;
         }
         return message;
     }

     /*
      * Finds the message with the provided key in the provided list.
      */

     private Message findMessage(Object key, ArrayList list) {
         for (int i = 0; i < list.size(); i++) {
             Message message = (Message) list.get(i);
             if (message.getKey().equals(key))
                 return message;
         }
         return null;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#update()
      */
     public void update() {
         // Update decorations
 for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
             ControlDecorator dec = (ControlDecorator) iter.next();
             dec.update();
         }
         // Update the form
 updateForm();
     }

     /*
      * Updates the container by rolling the messages up from the controls.
      */

     private void updateForm() {
         ArrayList mergedList = new ArrayList ();
         mergedList.addAll(messages);
         for (Enumeration enm = decorators.elements(); enm.hasMoreElements();) {
             ControlDecorator dec = (ControlDecorator) enm.nextElement();
             dec.addAll(mergedList);
         }
         update(mergedList);
     }

     private void update(ArrayList mergedList) {
         pruneControlDecorators();
         if (mergedList.isEmpty() || mergedList == null) {
             scrolledForm.setMessage(null, IMessageProvider.NONE);
             return;
         }
         ArrayList peers = createPeers(mergedList);
         int maxType = ((IMessage) peers.get(0)).getMessageType();
         String messageText;
         IMessage[] array = (IMessage[]) peers
                 .toArray(new IMessage[peers.size()]);
         if (peers.size() == 1 && ((Message) peers.get(0)).prefix == null) {
             // a single message
 IMessage message = (IMessage) peers.get(0);
             messageText = message.getMessage();
             scrolledForm.setMessage(messageText, maxType, array);
         } else {
             // show a summary message for the message
 // and list of errors for the details
 if (peers.size() > 1)
                 messageText = Messages.bind(
                         MULTIPLE_MESSAGE_SUMMARY_KEYS[maxType],
                         new String [] { peers.size() + "" }); //$NON-NLS-1$
 else
                 messageText = SINGLE_MESSAGE_SUMMARY_KEYS[maxType];
             scrolledForm.setMessage(messageText, maxType, array);
         }
     }

     private static String getFullMessage(IMessage message) {
         if (message.getPrefix() == null)
             return message.getMessage();
         return message.getPrefix() + message.getMessage();
     }

     private ArrayList createPeers(ArrayList messages) {
         ArrayList peers = new ArrayList ();
         int maxType = 0;
         for (int i = 0; i < messages.size(); i++) {
             Message message = (Message) messages.get(i);
             if (message.type > maxType) {
                 peers.clear();
                 maxType = message.type;
             }
             if (message.type == maxType)
                 peers.add(message);
         }
         return peers;
     }

     private String createDetails(ArrayList messages, boolean excludePrefix) {
         StringWriter sw = new StringWriter ();
         PrintWriter out = new PrintWriter (sw);

         for (int i = 0; i < messages.size(); i++) {
             if (i > 0)
                 out.println();
             IMessage m = (IMessage) messages.get(i);
             out.print(excludePrefix ? m.getMessage() : getFullMessage(m));
         }
         out.flush();
         return sw.toString();
     }

     public static String createDetails(IMessage[] messages) {
         if (messages == null || messages.length == 0)
             return null;
         StringWriter sw = new StringWriter ();
         PrintWriter out = new PrintWriter (sw);

         for (int i = 0; i < messages.length; i++) {
             if (i > 0)
                 out.println();
             out.print(getFullMessage(messages[i]));
         }
         out.flush();
         return sw.toString();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#createSummary(org.eclipse.ui.forms.IMessage[])
      */
     public String createSummary(IMessage[] messages) {
         return createDetails(messages);
     }

     private void pruneControlDecorators() {
         for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
             ControlDecorator dec = (ControlDecorator) iter.next();
             if (dec.isDisposed())
                 iter.remove();
         }
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#getMessagePrefixProvider()
      */
     public IMessagePrefixProvider getMessagePrefixProvider() {
         return prefixProvider;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#setMessagePrefixProvider(org.eclipse.ui.forms.IMessagePrefixProvider)
      */
     public void setMessagePrefixProvider(IMessagePrefixProvider provider) {
         this.prefixProvider = provider;
         for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
             ControlDecorator dec = (ControlDecorator) iter.next();
             dec.updatePrefix();
         }
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#getDecorationPosition()
      */
     public int getDecorationPosition() {
         return decorationPosition;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#setDecorationPosition(int)
      */
     public void setDecorationPosition(int position) {
         this.decorationPosition = position;
         for (Iterator iter = decorators.values().iterator(); iter.hasNext();) {
             ControlDecorator dec = (ControlDecorator) iter.next();
             dec.updatePosition();
         }
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#isAutoUpdate()
      */
     public boolean isAutoUpdate() {
         return autoUpdate;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.forms.IMessageManager#setAutoUpdate(boolean)
      */
     public void setAutoUpdate(boolean autoUpdate) {
         boolean needsUpdate = !this.autoUpdate && autoUpdate;
         this.autoUpdate = autoUpdate;
         if (needsUpdate)
             update();
     }
 }
